package com.chuanghai.light.tcp;

import com.chuanghai.light.service.WebSocketServer;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Set;

public class TCPServer{
    byte[] cmdTurnOn = new byte[]{0x24 ,0x58 ,0x5A ,0x03 ,0x00 ,0x0A ,0x00 ,0x04 ,0x03 ,0x06 ,0x00 ,0x02 ,0x00 ,0x01 , (byte) 0xE8,0x28};
    byte[] cmdTurnOff = new byte[]{0x24 ,0x58 ,0x5A ,0x03 ,0x00 ,0x0A ,0x00 ,0x11 ,0x03 ,0x06 ,0x00 ,0x01 ,0x00 ,0x00 , (byte)0xD9, (byte) 0xE8};
    byte[] cmdStatusRequest = new byte[]{0x24 ,0x58 ,0x5A ,0x03 ,0x00 ,0x0A ,0x00 ,0x18 , (byte) 0xF8,0x03 ,0x02 ,0x03 ,0x00 ,0x01 ,0x61 , (byte) 0xDB};
    byte[] cmdLogin = new byte[]{0x24 ,0x58 ,0x5A ,0x00 ,0x00 ,0x01 ,0x00};
// 24 58 5A 03 00 09 00 11 F8 03 02 40 03 55 91
    public MysqlTool mysqlTool;

    private  String hostAddress; // 服务器地址
    private  int myPort; // 服务器端口号
    private ServerSocket myServerSocket; // 服务器ServerSocket
    private Thread myThread; // 服务器线程
    private OnRequestListener requestListener; // 数据请求监听器
    private OnResponseListener responseListener; // 数据响应监听
    private OnSocketChange socketChangeListener; // 客户端状态改变监听器
    private ClientRunner clientRunner; // 客户端处理线程

    private Parser parser = new Parser();
    public static HashMap<String, Socket> clients = new HashMap<>(); // 在线客户端列表<客户端编号,客户端对象>
    public static HashMap<String, Integer> wakeUpTime = new HashMap<>(); // 如果没收到心跳或者保活消息，值会累加，超过一定值表示离线了
    public LightControlThread lightControlThread = new LightControlThread( "LightControlThread");



//    public TCPServer(int port) {
//        this(null, port);
//    }
//
//    public TCPServer(String hostAddress, int port) {
//        this.hostAddress = hostAddress;
//        this.myPort = port;
//        mysqlTool = new MysqlTool();
//        clientRunner = new ClientRunner();
//    }

    public HashMap<String, Socket> getClients()
    {
        return clients;
    }

    public void startRun(int port) {
        this.hostAddress = null;//hostAddress;
        this.myPort = port;
        mysqlTool = new MysqlTool();
        clientRunner = new ClientRunner();
        lightControlThread.start();
    }

    /**
     * 安全关闭ServerSocket
     *
     * @param serverSocket
     */
    private static final void safeClose(ServerSocket serverSocket) {
        if (serverSocket != null) {
            try {
                serverSocket.close();
            } catch (IOException e) {
            }
        }
    }

    /**
     * 安全关闭Socket
     *
     * @param socket
     */
    private static final void safeClose(Socket socket) {
        if (socket != null) {
            try {
                socket.close();
            } catch (IOException e) {
            }
        }
    }

    /**
     * 安全关闭BufferedReader
     *
     * @param is
     */
    private static void safeClose(InputStream is) {
        if (is != null) {
            try {
                is.close();
            } catch (IOException ignored) {
            }
        }
    }

    /**
     * 安全关闭PrintWriter
     *
     * @param writer
     */
    private static final void safeClose(PrintWriter writer) {
        writer.close();
    }

    /**
     * 开始TCP服务
     */
    public void start() {
        try {
            myServerSocket = new ServerSocket();
            myServerSocket.bind((hostAddress != null) ? new InetSocketAddress(hostAddress, myPort) : new InetSocketAddress(myPort));
        } catch (IOException e) {
            System.out.println("端口被占用");
        }

        myThread = new Thread(new Runnable() {
            int disConnectCount=0;
            byte resData;
            @Override
            public void run() {
                do {
                    try {
                        final Socket finalAccept = myServerSocket.accept(); // 监听客户端连接
                        clientRunner.exec(() -> {
                            InputStream inputStream = null;
                            try {
                                inputStream = finalAccept.getInputStream();
                                String remoteAddress = finalAccept.getRemoteSocketAddress().toString();
                                int j = remoteAddress.indexOf(":"); // 找分隔符的位置
                                String ip = remoteAddress.substring(1, j);  // 找到分隔符，截取子字符串
                                String pointCode = mysqlTool.getPointCodeByIp(ip);  // 根据IP查询是哪个点位
                                System.out.println(pointCode+"   "+ip);
                                clients.put(pointCode, finalAccept);
                                wakeUpTime.put(pointCode,0);
                                mysqlTool.updateOnlineByPointCode(pointCode, true);  // 修改网络状态
                                WebSocketServer.BroadCastInfo("{\"pointCode\":\""+pointCode+"\",\"online\":1}");
                                System.out.println(pointCode+" connected successfully，The number of online is："+clients.size());
                                clientRunner.code = pointCode;
                                disConnectCount = 0;
                                while (!finalAccept.isClosed()) {
                                    Packet packet = null;
                                    do {
                                        resData = (byte) inputStream.read();
                                        if(resData==-1)
                                        {
                                            disConnectCount++;
                                            if(disConnectCount>5){
                                                System.out.println("device outLine...");
                                                throw new IOException();
                                            }
                                        }else{
                                            disConnectCount=0;
                                        }
                                        packet = parser.message_parse_char(resData);
                                    } while (packet == null);

                                    byte[] bytes = new byte[packet.len + 6];  // 转换成数组
                                    packet.toBytes(bytes);
                                    System.out.println("msg : "+Arrays.toString(bytes));
                                    // 设备上来的心跳包，消息原样返回
                                    if(packet.type== 0x02 && packet.len==0x0C)
                                    {
                                        finalAccept.getOutputStream().write(bytes, 0, bytes.length);
                                        mysqlTool.updateOnlineByPointCode(pointCode, true);  // 修改网络状态
                                        WebSocketServer.BroadCastInfo("{\"pointCode\":\""+pointCode+"\",\"online\":1}");
                                        Date day=new Date();
                                        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                                        System.out.println(pointCode+" heart1  "+df.format(day));
                                        wakeUpTime.put(pointCode,0);
                                    }
                                    // 设备上报状态（保活）
                                    else if(packet.type== 0x02 && packet.len==0x0B)
                                    {
                                        finalAccept.getOutputStream().write(bytes, 0, bytes.length);
                                        mysqlTool.updateOnlineByPointCode(pointCode, true);  // 修改网络状态
                                        WebSocketServer.BroadCastInfo("{\"pointCode\":\""+pointCode+"\",\"online\":1}");
                                        Date day=new Date();
                                        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                                        System.out.println(pointCode+" heart2  "+df.format(day));
                                        wakeUpTime.put(pointCode,0);
                                    }
                                    else if(packet.type== 0x03 && packet.len==0x09)
                                    {
                                        //finalAccept.getOutputStream().write(bytes, 0, bytes.length);
                                        // 状态查询返回 24 58 5A 03 00 09 00 15 F8 03 02 40 07 54 52  倒数第3位表示状态，用位表示，1表示开，0表示关
                                        System.out.println(pointCode+" : "+bytes[12]);
                                    }
                                    // 设备登录上线
                                    else if(packet.type== 0x00 && packet.len==0x1C)
                                    {
                                        finalAccept.getOutputStream().write(cmdLogin, 0, cmdLogin.length);
                                    }
                                    // 主动请求的开关状态的回复消息
                                    else if(packet.type== 0x03 && packet.len==0x1F)
                                    {
                                        // 由于几个是同时操作，所以第一个回路的开关状态就表示这个点位的状态
                                        if(packet.payload.getData().get(6)==1)          mysqlTool.updateLightStateByPointCode(pointCode, true);
                                        else if(packet.payload.getData().get(6)==0)     mysqlTool.updateLightStateByPointCode(pointCode, false);
                                        System.out.println(pointCode+" point device state is:"+ packet.payload.getData().get(6));
                                    }
                                    // 设置开、关的回复消息
                                    else if(packet.type== 0x03 && packet.len==0x0A)
                                    {
                                        // 由于几个是同时操作，所以第一个回路的开关状态就表示这个点位的状态
                                        if(packet.payload.getData().get(7)==1)          mysqlTool.updateLightStateByPointCode(pointCode, true);
                                        else if(packet.payload.getData().get(7)==0)     mysqlTool.updateLightStateByPointCode(pointCode, false);
                                        System.out.println(pointCode+" point device state is:"+ packet.payload.getData().get(7));
                                        WebSocketServer.BroadCastInfo("{\"pointCode\":\""+pointCode+"\",\"state\":"+packet.payload.getData().get(7)+"}");
                                    }
                                }
                            } catch (IOException e) {
                                e.printStackTrace();
                                System.out.println("device outLine...");
                                safeClose(inputStream);
                                safeClose(finalAccept);
                                mysqlTool.updateOnlineByPointCode(clientRunner.code, false);
                                removeSocket(clientRunner.code);
                            }
                        });
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                } while (!myServerSocket.isClosed());
            }
        });
        myThread.setDaemon(true);
        myThread.setName("TcpServer Main Listener");
        myThread.start();
    }

    private boolean isConnected(Socket socket) {
        try {
            socket.sendUrgentData(0xff);
            return true;
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
    }

    public synchronized void removeSocket(String code) {
        safeClose(clients.get(code));
        clients.remove(code);
        wakeUpTime.remove(code);
    }


    /**
     * 停止TCP服务
     */
    public void stop() {
        try {
            safeClose(myServerSocket);
            if (myThread != null) {
                myThread.join();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 监听服务异常结束
     */
    public void waitServerStop() {
        try {
            myThread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }



    /**
     * 客户端处理线程
     */
    public static class ClientRunner {
        private long requestCount;
        public String code;

        public void exec(Runnable code) {
            ++requestCount;
            Thread t = new Thread(code);
            t.setDaemon(true);
            t.setName("TcpServer thread (#" + requestCount + ")");
            t.start();
        }
    }

    /**
     * 监听客户端请求，并可以修改请求内容
     */
    public interface OnRequestListener {
        String onRequestListener(String request);
    }

    /**
     * 设置客户端请求监听器
     *
     * @param listener
     */
    public void setOnRequestListener(OnRequestListener listener) {
        this.requestListener = listener;
    }

    /**
     * 监听网关响应数据，并可以修改请求内容
     */
    public interface OnResponseListener {
        String onResponseListener(String response);
    }

    /**
     * 设置网关响应监听器
     *
     * @param listener
     */
    public void setOnResponseListener(OnResponseListener listener) {
        this.responseListener = listener;
    }

    public interface OnSocketChange {
        void onSocketChange(Set<String> hostAddress);
    }

    public void setOnSocketChange(OnSocketChange listener) {
        this.socketChangeListener = listener;
    }


    //-----------------------------------------------------------------------------------------

    /**
     * 发送控制命令，设置成功返回true，失败返回false
     * @param pointCode
     * @param state
     * @return
     */
    public boolean setLightState(String pointCode,boolean state){
        try {
            if(clients.size()>0)
            {
                Socket socket = clients.get(pointCode);
                if(socket!=null) {
                    if(state==true) socket.getOutputStream().write(cmdTurnOn, 0, cmdTurnOn.length);
                    else            socket.getOutputStream().write(cmdTurnOff, 0, cmdTurnOff.length);
                    return true;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }


    /**
     * 获取指定点位灯的状态
     * @param pointCode
     * @return
     */
    public boolean getLightState(String pointCode){
        try {
            if(clients.size()>0)
            {
                Socket socket = clients.get(pointCode);
                if(socket!=null) {
                    socket.getOutputStream().write(cmdStatusRequest, 0, cmdStatusRequest.length);
                    return true;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 发送控制命令，设置成功返回true，失败返回false
     * @param state
     * @return
     */
    public boolean setAllLightState(boolean state){
        try {
                if(clients.size()>0) {
                    for (HashMap.Entry<String, Socket> entry : clients.entrySet()) {
                        //System.out.println("key = " + entry.getKey() + ", value = " + entry.getValue())
                        Socket socket = entry.getValue();
                        if (state == true)      socket.getOutputStream().write(cmdTurnOn, 0, cmdTurnOn.length);
                        else                    socket.getOutputStream().write(cmdTurnOff, 0, cmdTurnOff.length);
                    }
                }
            } catch (IOException e1) {
            e1.printStackTrace();
        }
        return false;
    }




}
